package guid

import (
	"context"
	"fmt"
	"github.com/sirupsen/logrus"
	"gorm.io/driver/mysql"
	"gorm.io/driver/postgres"
	"gorm.io/gorm"
	"os"
	"time"
)

type DBType int

const (
	_ DBType = iota
	DBTypePG
	DBTypeMysql
)
const dbExpiration = time.Minute

type modelMyId struct {
	Id          int
	HostnamePid string `gorm:"index"`
	CreateAt    time.Time
	ExpireAt    time.Time `gorm:"index"`
}

func (modelMyId) TableName() string {
	return "guid"
}

var (
	ctx    context.Context
	cancel context.CancelFunc
)

func InitWithDB(dbType DBType, dsn string) (err error) {
	defer func() {
		if err != nil {
			err = fmt.Errorf("[guid]初始化失败:%w", err)
		}
	}()
	if cancel != nil {
		cancel()
	}
	ctx, cancel = context.WithCancel(context.Background())

	var dialector gorm.Dialector
	switch dbType {
	case DBTypePG:
		dialector = postgres.Open(dsn)
	case DBTypeMysql:
		dialector = mysql.Open(dsn)
	}

	db, err := gorm.Open(dialector, &gorm.Config{})
	if err != nil {
		return
	}
	err = db.AutoMigrate(&modelMyId{})
	if err != nil {
		return
	}

	err = db.Transaction(func(tx *gorm.DB) error {
		var list []modelMyId
		idMap := map[int]bool{}

		err = tx.Exec("LOCK TABLE guid").Error
		if err != nil {
			return err
		}
		err = tx.Delete(&modelMyId{}, "expire_at < ?", time.Now()).Error
		if err != nil {
			return err
		}

		err = tx.Find(&list).Error
		if err != nil {
			return err
		}

		for _, one := range list {
			idMap[one.Id] = true
		}
		maxWorker := int(MaxWorker)
		for i := 1; i < maxWorker; i++ {
			if _, ok := idMap[i]; !ok {
				projectId = i
				break
			}
		}

		if projectId == 0 {
			err = fmt.Errorf("worker数量达到最大(%d)", MaxWorker)
			return err
		}
		hostname, _ := os.Hostname()
		pid := os.Getpid()

		err = tx.Create(&modelMyId{
			Id:          projectId,
			HostnamePid: fmt.Sprintf("%s-%d", hostname, pid),
			CreateAt:    time.Now(),
			ExpireAt:    time.Now().Add(dbExpiration),
		}).Error
		if err != nil {
			return err
		}

		return nil
	})
	if err != nil {
		return
	}

	go func() {
		for {
			if ctx.Err() != nil {
				return
			}

			time.Sleep(time.Second * 10)

			err := db.Model(&modelMyId{}).
				Where("id = ?", projectId).
				Update("expire_at", time.Now().Add(dbExpiration)).Error

			if err != nil {
				logrus.Errorf("[guid]更新失败:\n%s", err)
			}
		}
	}()

	return
}
